﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using System.Linq;
using TaskManager.Domain.Exceptions;
using TaskManager.Domain.Repositories;
using TaskManager.Domain.Services;

namespace TaskManager.Domain.BusinessObjects
{
    /// <summary>
    /// Represents a project
    /// </summary>
    public class Project: IEntity<int>
    {
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(Project));

        /// <summary>
        /// User action logger
        /// </summary>
        public IUserActionLogger Logger { get; set; }

        /// <summary>
        /// Project identifier
        /// </summary>
        [Key]
        public int Id { get; set; }

        /// <summary>
        /// Project name
        /// </summary>
        [MaxLength(50)]
        [Required]
        public string Name { get; set; }

        /// <summary>
        /// Tasks
        /// </summary>
        /// <remarks>Lazy-loaded property</remarks>
        public virtual ICollection<Task> Tasks { get; private set; }

        /// <summary>
        /// Project log
        /// </summary>
        /// <remarks>Lazy-loaded property</remarks>
        public virtual ICollection<ProjectLogEntry> Logs { get; private set; }

        /// <summary>
        /// Description
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Project state
        /// </summary>
        [NotMapped]
        public ProjectState State
        {
            get { return (ProjectState)StateImpl; }
            private set { StateImpl = (int)value; }
        }

        /// <summary>
        /// Project state implementation
        /// </summary>
        /// <remarks>EF CF1 does not support mapping for enumerations,so we need to introduce 
        /// internal property for the sake of implementation</remarks>
        public int StateImpl { get; private set; }

        /// <summary>
        /// Date and time of creation
        /// </summary>
        public DateTime Created { get; private set; }

        /// <summary>
        /// Last modification date and time
        /// </summary>
        public DateTime Updated { get; private set; }

        /// <summary>
        /// Timestamp to prevent concurrency modifications
        /// </summary>
        [Timestamp]
        public byte[] RowVersion { get; set; }

        /// <summary>
        /// Parameterless constructor
        /// </summary>
        public Project()
        {
            Tasks = new List<Task>();
            Logs = new List<ProjectLogEntry>();

            if (Log.IsDebugEnabled) Log.Debug("Project parameterless constructor");
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">Project name</param>
        /// <param name="description">Description</param>
        /// <param name="logger">Logger</param>
        public Project(string name, string description, IUserActionLogger logger)
        {
            Contract.Requires(!string.IsNullOrEmpty(name));
            Contract.Requires(logger != null);

            Tasks = new List<Task>();
            Logs = new List<ProjectLogEntry>();
            Name = name;
            Description = description;
            Updated = Created = DateTime.Now;
            State = ProjectState.InProgress;
            Logger = logger;

            Logger.LogCreateProject(this);

            if (Log.IsInfoEnabled) Log.Debug("Created project.Name=[" + name + "]");
        }

        /// <summary>
        /// Creates new task and adds it in list of project's tasks
        /// </summary>
        internal void AddTask(Task task)
        {
            Contract.Requires(State == ProjectState.InProgress);

            Updated = DateTime.Now;

            Tasks.Add(task);
        }

        /// <summary>
        /// Deletes a task
        /// </summary>
        /// <param name="task">Task</param>
        internal void DeleteTask(Task task)
        {
            Contract.Requires(State == ProjectState.InProgress);

            Project project = task.Project;

            var taskObj = Tasks.Where(t => t.Id == task.Id).Single();

            Tasks.Remove(taskObj);

            Updated = DateTime.Now;

            Logger.LogDeleteTask(project, task.Summary);

            if (Log.IsInfoEnabled) Log.Info("Deleted task.Summary=[" + taskObj.Summary + "]");
        }

        /// <summary>
        /// Finalizes the project
        /// </summary>
        public void Close()
        {
            Contract.Requires(State == ProjectState.InProgress);
            Contract.Ensures(State == ProjectState.Finished);

            Tuple<bool, string> validationResult = ProjectValidationRules.PermitClose(this);

            if (!validationResult.Item1)
            {
                throw new UserActionFailureException(validationResult.Item2);
            }

            State = ProjectState.Finished;

            Updated = DateTime.Now;

            Logger.LogCloseProject(this);

            if (Log.IsInfoEnabled) Log.Info("Closed project.Name=[" + Name + "]");
        }

        public void Reopen()
        {
            Contract.Requires(State == ProjectState.Finished);
            Contract.Ensures(State == ProjectState.InProgress);

            State = ProjectState.InProgress;

            Logger.LogReopenProject(this);

            if (Log.IsInfoEnabled) Log.Info("Reopened project.Name=[" + Name + "]");
        }
    }
}